How to Implement Flexible UI Components

Table of Contents

How to Implement Flexible UI Components

Overview

Flexible UI Components

Layout Controller

Flavor

Collection Renderer

Resource Renderer

Property Renderer

Components

Required Libraries

Implementation

Layout Controller

Collection Renderer

Introduction

Coding in Detail

Examples

Resource Renderer

Introduction

Coding in Detail

Property Renderer

Introduction

Examples

Commands

Configuration

Introduction

Property Renderer (Mapping)

Resource Renderer (Mapping)

Resource Renderer Mapping

Resource Renderer Settings

Collection Renderer

Collection Renderer Mapping

Collection Renderer Settings

Layout Controller

Mapping

Layout Set

Details Menu

Resource Menu

Menu (in a Custom Position)

Command

Command Group

Parameters




How to Implement Flexible UI Components

Overview

This guide describes how to implement, deploy, and configure custom components for the KM flexible user interface. It covers LayoutController, CollectionRenderer, ResourceRenderer and PropertyRenderer. The first part of the guide presents an overview of the flexible UI components mentioned above, and continues with a detailed section containing examples of how they can be implemented. The latter part of the guide shows how these components can be configured and integrated into your application.

For background information on flexible UI components, see help.sap.com -> Information Integration ->Knowledge Management -> Administration Guide -> System Administration -> Content Management Configuration -> User Interface

Flexible UI Components

Layout Controller

A layout controller defines the number and position of the screen areas in an iView.

You use layout controllers in the definition of a layout set.

To call up an overview of the available layout controllers in the standard delivery, choose Content Management -> User Interface -> Mapping -> Layout Controllers.

Layout controllers are based on Java classes. The screen areas used in a layout controller, the assigned flavors, and the position of the screen areas are defined in the Java classes.

Flavor

A flavor addresses the individual areas in a layout controller.

In a layout controller, you can assign flavors to individual screen areas. However, you do not always need to specify a flavor for every screen area. In a simple layout controller (for example, SimpleLayoutController) you can also define screen areas without a flavor.

The aliases of the layout controllers are built similarly to the aliases of the delivered flavors.

A layout set typically contains a set of collection renderer settings for each area. You may want to use different resource renderer settings for the individual screen areas.

If the settings are only to be valid for a specific screen area, you assign a flavor to each individual set of collection renderer settings and resource renderer settings.

Although the flavors and collection renderers have similar names, there is nothing to stop you from using a collection renderer of the type CollectionListRenderer in a screen area with the flavor Grid. Flavors denote a single screen area.

For a list of flavors used in the system, see Content Management -> User Interface -> Mapping -> Flavor.

Collection Renderer

A collection renderer is responsible for the display of multiple resources on the screen. It groups the various elements to be displayed and generates the screen display according to defined parameters.

Collection renderers are based on Java classes, which you can configure extensively using collection renderer settings. You specify the collection renderer settings in the definition of a layout set.

To call up an overview of the available collection renderers, choose Content Management -> User Interface -> Mapping -> Collection Renderers.

Resource Renderer

A resource renderer displays a single resource (file, folder, or link) on the screen. It groups the various elements of a resource and generates the screen display according to defined parameters. The elements of a resource include symbols and texts, for example.

Resource renderers are based on Java classes, which you can configure extensively using resource renderer settings. You specify the resource renderer settings in the definition of a layout set.

To call up an overview of the available resource renderers, choose Content Management -> User Interface -> Mapping -> Resource Renderers.

Property Renderer

A property is a feature of a resource that can be displayed in the Navigation iView. You can define a property in the portal in the Configuration area (the procedure is explained in detail later on). A property renderer is responsible for the display of a property of a given resource on the screen. It customizes the property in question and displays the relevant property icon, toolbox, and/or other elements connected to it, in the Navigation iView.

Property renderers are Java classes which can be configured extensively. Because a property renderer deals with the display of a given property, it is very important that you become familiarized with the htmlb library, which is used extensively in such an application.

To call up an overview of the available property renderers, choose the following path in the portal: System Administration ->System Configuration -> Configuration ->Content Management -> Global Services -> Property Metadata -> Property Renderer.

Components

Components are reusable elements of the user interface that provide functions such as sort functions or current path specifications.

You enter the components to be displayed with the collection on the user interface in the configuration of collection renderer settings in the parameter Components.

To call up an overview of the available components, choose Content Management -> User Interface -> Mapping -> Components.

Required Libraries

You need to add the following JAR-files to your Eclipse build path libraries.

(The expression <portalapps> represents the following path of my example EP6 SP3 installation "\usr\sap\b12\JC00\j2ee\cluster\server0\apps\sap.com\irj\servlet_jsp\irj\root\WEB-INF\portal\portalapps")

HTMLB

<portalapps>\com.sap.portal.htmlb\lib\htmlb.jar

CM and FlexUI

<portalapps>\com.sap.netweaver.bc.util\lib\bc.util.public_api.jar
<portalapps>\com.sap.km.cm.ui.flex\lib\* (complete folder recommended)
km.shared.ui.flex.base_api.jar
km.shared.ui.flex.util_api.jar
km.shared.ui.flex.resource_api.jar
km.shared.ui.flex.enum_api.jar
km.shared.ui.flex.layout_api.jar (LayoutController)
km.shared.ui.flex.control_api.jar (LayoutController)

<portalapps>\com.sap.km.cm.ui.flex\private\lib\km.appl.ui.flex.control_core.jar (LayoutController)

This library has not yet been released, and is therefore not available in your application.

<portalapps>\com.sap.km.bs.ui.wdf\lib\bc.wdf.ui.framework_api.jar (Layout)

<portalapps>\com.sap.km.cm.ui\lib\ (complete folder recommended)
km.shared.ui.base_api.jar
km.shared.ui.util_api.jar

<portalapps>\com.sap.km.cm.repository.service.base\lib\km.shared.repository.service.layout_api.jar
<portalapps>\com.sap.netweaver.bc.util\lib\bc.util.public_api.jar
<portalapps>\com.sap.netweaver.bc.rf\lib\bc.rf.framework_api.jar
<portalapps>\com.sap.km.cm.ui\lib\km.shared.ui.util_api.jar
<portalapps>\com.sap.km.cm.service.base\lib\km.shared.service.propertyconfig_api.jar

UICommand

<portalapps>\com.sap.km.cm.ui.flex\lib\km.shared.ui.flex.uicommand_api.jar
<portalapps>\com.sap.netweaver.bc.rf.service\lib\bc.rf.global.service.oth_api.jar

Logging

<J2EE-Root>\j2ee\admin\lib\logging.jar

Bootstrap

<portalapps>\com.sap.netweaver.bc.crt\lib\bc.crt_api.jar

Control

<portalapps>\ com.sap.km.cm.ui\lib\km.shared.ui.event_api.jar

<portalapps>\com.sap.portal.runtime.config\lib\bc.cfg_api.jar

Implementation

A layout controller is used to define the number and position of screen areas in an iView. When you implement a layout controller, you have to:

Layout Controller

Choose one of the following options:

com.sapportals.wcm.rendering.layout.ILayoutController

com.sapportals.wcm.rendering.layout.AbstractLayoutController

Overwrite at least the following methods of AbstractLayoutController:

getNewInstance(): Returns a new instance of this layout controller

render(): This method is called by the framework when the rendering action is executed

getControls(): Returns a list of controls that are rendered. The control array should contain all area elements with their flavors.

Coding in detail:

public class MyLayoutController extends AbstractLayoutController {
public ILayoutController getNewInstance() {
return new MyLayoutController();
}
public Control[] getControls() {
if (this.controls == null) this.createControls();
return this.controls;
}
public Component render() throws WdfException {
GridLayout result = new GridLayout();
result.setHeightPercentage(100);
result.addComponent(1, 1, this.controls[0].render());
result.setWidth("100%");
return result;
}
private void createControls() {
this.controls = new Control[1];
//NeutralControl control = new NeutralControl(this.getProxy(), /* IFlavorConst.FLAVOR_LIST.toString());
MyControl control = new MyControl(this.getProxy(), IFlavorConst.FLAVOR_LIST.toString());
this.controls[0] = control;
}
}

NeutralControl is not available for applications other than KM core applications.

Collection Renderer

Introduction

A collection renderer is responsible for displaying multiple resources on the screen. It groups the various elements to be displayed and generates the screen display according to defined parameters.

Choose one of the following options:

com.sapportals.wcm.rendering.collection.ICollectionRenderer

com.sapportals.wcm.rendering.collection.LightCollectionRenderer

Overwrite at least the following methods of LightCollectionRenderer:

renderUI(): This method is called by the framework when the rendering action needs to be carried out

getNewInstance(): Returns a new instance of the collection renderer

Coding in Detail

Here is the basic implementation of the renderUI() method for a collection renderer.

public class MyCollectionRenderer extends LightCollectionRenderer {

public Component renderUI() throws WcmException {
GridLayout grid = new GridLayout();
int row = 1;
for (int i = 0; i < this.getResourceList().size(); i++) {
IResource res = this.getResourceList().get(i);
grid.addComponent(row++, 1, this.getRenderer(res).render());
}
return grid;
}
public ILayoutObject getNewInstance() {
return this.initNewInstance(new MyCollectionRenderer());
}
}

Examples

When implementing a collection renderer, you usually only implement the overall layout and let the resource renderer handle the display of the properties. However, if you want more flexibility in the display of the properties, you can choose to use the collection renderer to render each individual property of resources. Below is an example of such a collection renderer, which offers a high degree of flexibility.

Setup

The task is to create a grid collection renderer that displays all the elements in the iView aligned by line and column for each resource. The collection renderer should also respect the configuration for the modifiers in the resource renderer configuration in the portal. For this purpose, you have to render each property of a resource in a cell of the grid.

Convention: The properties connected by the character '+' are displayed on the same row, and those connected by the character ',' are displayed on the following row.

Note that the CollectionGridRenderer available in the standard delivery renders an entire resource in a cell of the grid, which leaves the resource renderer responsible for rendering the properties of the resource.

Requirements

This functionality is implemented in the renderUI method.

Before beginning the implementation of the renderUI() method, you need to set the supported parameters. Here is the list of parameters that are set in this case:

parameters.setParameter(BREADCRUMBSTYLE, BREADCRUMBSTYLE_HORIZONTAL);
parameters.setParameter(BREADCRUMBVISIBILITYSTYLE, BREADCRUMBVISIBILITYSTYLE_STANDARD);
parameters.setParameter(SHOWFILESSTYLE, SHOWFILESSTYLE_ALL);
parameters.setParameter(LINKSSTYLE, LINKSSTYLE_ALL);
parameters.setParameter(SHOWFOLDERSSTYLE, SHOWFOLDERSSTYLE_ALL);
parameters.setParameter(MASSACTIONSTYLE, MASSACTIONSTYLE_OFF); parameters.setParameter(IResourceRendererParameterNameConst.ITEMACTIONSTYLE, IResourceRendererParameterNameConst.ITEMACTIONSTYLE_HOVER);
parameters.setParameter(SHOWFOLDERTITLE, false);
parameters.setParameter(IParameterName.GRIDORDERSTYLE, IParameterName.GRIDORDERSTYLE_COLUMN_MAJOR);
parameters.setParameter(ROWS, numberOfRows);
parameters.setParameter(COLUMNS, this.numberOfColumns);
parameters.setParameter(ROWSPACING, 0);
parameters.setParameter(COLUMNSPACING, 0);
parameters.setParameter(ITEMSELECTIONMODE, ITEMSELECTIONMODE_OFF);
parameters.setParameter(ROWBACKGROUNDSTYLE, ROWSPACINGSTYLE_TRANSPARENT);
parameters.setParameter(COLLECTION_ACTIONSTYLE, COLLECTION_ACTIONSTYLE_HOVER);
parameters.setParameter(PROPERTYCOLUMNS, DEFAULT_PROPERTY_COLUMNS);
parameters.setParameter(RESOURCE_LIST_FILTER, RESOURCE_LIST_FILTER_DEFAULT);
parameters.setParameter(SHOW_HIDDEN, false);
parameters.setParameter(RESIZEHEIGHTSTYLE, RESIZEHEIGHTSTYLE_COMPACT);
parameters.setParameter(RESIZEWIDHTSTYLE, RESIZEWIDHTSTYLE_COMPACT);
parameters.setParameter(PAGER_LINK_COUNT,AllignmentFormCollectionRenderer.PAGER_LINKS_DEFAULT);

Background Information

In order to be able to render the properties of a resource, you need to familiarize yourself with the modifiers available for a property and the way in which they can be used. The position modifier can be used to specify a certain position for a component (in our case a property of a resource) to be rendered. With the position modifier one can also specify column or row span for the cell of a property. Also with the help of modifiers, one can specify sell spacing or padding and different other formatting details for a property. In this example the modifier specifications will very helpful since the renderer is based especially on the position modifier.

Implementation

Below are several basic coding tools that will help you in implementing this collection renderer.

//get the i-th resource from the list of resources
IResource res = this.getResourceList().get(i);
//get the list of properties in a IPropertyColumn[] object
IPropertyColumn[] propertyCell = this.getColumnsList();

Next, you need the resource renderer used to get the properties for the resources:

IResourceRenderer renderer = this.getRenderer(res);

In order to obtain a property (or list of properties) to be displayed in the same row (included between two commas), you can use the following line of code:

IPropertyWithModifiersList propertyWithModifiers = propertyCell[k].getPropertyWithModifier();

Since it is part of the task of this collection renderer to provide full flexibility, the Form layout was chosen instead of the Grid Layout as the background structure for the rendered objects. This choice is due to the fact that the GridLayout class does not offer the possibility of setting a row span, while the FormLayout class offers the possibility of setting both a row and a column span.

Algorithm

As for this collection renderer you need to render each property in a different cell of the grid, you have to determine several strict positioning rules for each component (property) for each resource to be rendered. When deciding on these rules you also have to take into account the row or column spans a property cell might have. One should note that, if there is say a property with a row span, if you render another property on the same line the number of columns will increase by one whereas if you render another property on a different line the number of columns does not increase. In the following figure you can see an example of this feature:

(1 - 4,1)

(1,2)

(2,1)

(3,1)

(4,1)

Below is the set of rules used for deciding on the position of any property given the position modifier:

//takes the position modifier
position = PropertyColumnFactory.getInstance().getPosition(propertyCell[k]);

if (position != null) {
int positionRow = position.getRow();
int positionCol = position.getColumn();
columnSpan = position.getSpanColumn();
rowSpan = position.getSpanRow();

//hold out the number of the row on which a row span was set
//I do this because on this row there is a real cell: the number of columns increases
//while on the following rows it is just a span and the column is not increased
if (rowSpan > 1) {
span++;
rowWithSpan = (positionRow + startingRow) - 1;
}

//if the current row is different than the row specified in the position modifier then update
if (((positionRow + startingRow) - 1) != row) {
row = (positionRow + startingRow) - 1;
}

//to update the column correctly we need to check if we are on a row with span or not
if (row == rowWithSpan) {
//if the current column is different than the column specified in the position modifier then update
if (col != (positionCol + (resource * (max)))) {
col = positionCol + (resource * (max));
}
} else {
//if we are not on a row with span you calculate the column so that you account for the previously set row spans
//nbOfSpaces holds the number of row spans that exist
if (col != ((positionCol + (resource * (max - 1))) - nbOfSpaces)) {
col = (positionCol + (resource * (max - 1))) - nbOfSpaces;
}
}
}

In the code above, resource represents the number of the resource on this row (of resources) and max represents the maximum number of columns that a resource can have. Note that the variable max was constructed in such a way that it takes into consideration the row background style set in the configuration. This is because for the possible styles the number of cells needed to create the resource separators differ. Here is the initialization of max:

//in case of alternating style, the resources are separated by an empty image, 
//half colored as the previous resource and half as the next resource, so two cells colored differently
if (alternatingStyle) {
//add 2 to the max number of columns to leave space for the separating items
max = getColumnsPerResource(propertyCell) + 2;
} else {
//in this case the resources are separated by: half an empty image+line+half an empty image
if (lineSeparatorStyle) {
//add 3 to the max number of columns to leave space for the separating items
max = getColumnsPerResource(propertyCell) + 3;
}
//in the standard style the resources are separated by an empty image
else {
//add 1 to the max number of columns to leave space for the separating items
max = getColumnsPerResource(propertyCell) + 1;

The maximum number of columns that a resource can have is calculated as follows:

/**
* method that computes the maximum number of columns a resource can
occupy including column spans
*/
public int getColumnsPerResource(IPropertyColumn[] prop) {
IPropertyPosition position = null;
int max = 0;
int positionCol = 1;
HashMap hm = new HashMap();
for (int k = 0; k < prop.length; k++) {
//a property together with its modifiers
//IPropertyWithModifiersList propertyWithModifiers = prop[k].getPropertyWithModifier();
//want to do the position modifier
position = PropertyColumnFactory.getInstance().getPosition(prop[k]);

if (position != null) {
positionCol = position.getColumn();
int columnSpan = position.getSpanColumn();
hm = getTileAsLableColumns(prop);
if (positionCol > max) {
max = positionCol;
}

if (((positionCol + columnSpan) - 1) > max) {
max = (positionCol + columnSpan) - 1;
}
}
}

return max+hm.size();
}
If the position modifier is absent, then the next property is rendered on the next row at column 1. This is how you can handle this case:

else{
if (rowWithSpan >0){
if (row == rowWithSpan){
if (col != (( + (resource * max)) - nbOfSpaces)) {
col = (1 + (resource * max)) - nbOfSpaces;
}
}
else {
if ((row != rowWithSpan) && (spanBefore+startingRow-1 >= row)){
//if we are not on a row with span, you calculate the column so that you account for the previously set row spans
//nbOfSpaces holds the number of row spans that exist
if (col != ((1 + (resource * (max - 1))) - nbOfSpaces)) {
col = (1 + (resource * (max - 1))) - nbOfSpaces;
}
}
else{
if (col != ((1 + (resource * max)) - nbOfSpaces)) {
col = (1 + (resource * max)) - nbOfSpaces;
}
}
}
}else{
if (col != ((1 + (resource * max)) - nbOfSpaces)) {
col = (1 + (resource * max)) - nbOfSpaces;
}
}
}

In the code above, the variable resource counts the number of the resource on the current row of resources; the variable nbOfSpaces counts the number of resource separator spaces that were added (this is needed because the separator spaces are set on the first cell with the appropriate row span), rowWithSpan holds the row on which a row span was set, and startingRow is the first row of the resource(also the row on which the separator cells are rendered).

When rendering a new component, you need to distinguish between several cases:

If there are two properties connected by "+" in the user's configuration, they are displayed in the same cell of the grid. The following method provides this functionality:

/**
* method that creates a small grid to render properties connected by "+"
* the grid returned by this method is rendered in a cell of our collection renderer grid
* @param propertyWithModifiers
* @param renderer
* @return
*/
public Component getGridCell(IPropertyWithModifiersList propertyWithModifiers, IResourceRenderer renderer) {
IMetaName metaName = null;
GridLayout grid = new GridLayout();
int col = 1;
boolean contentLink = false;
try {
//iterate through the properties
IPropertyWithModifiersListIterator iter = propertyWithModifiers.listIterator();
while (iter.hasNext()) {
IPropertyWithModifiers aPropertyWithModifiers = iter.next();
metaName = aPropertyWithModifiers.getMetaName();
//check whether it is a content link modifier
contentLink = aPropertyWithModifiers.isModifierSet(IPropertyModifierName.CONTENT_LINK);
//place the properties in the small grid
if (metaName != null) {
Component comp = renderer.renderMetaProperty(metaName, contentLink);
grid.addColSpanComponent(1, col++, comp, 1);
grid.getCell(1, col - 1).setVAlignment(CellVAlign.TOP);
}
}
} catch (WcmException wcmEx) {
log.debugT("could not render row " + LoggingFormatter.extractCallstack(wcmEx));
}
return grid;
}

If the SetTitleAsLable modifier is set, you have to extract the name of the property and render it in a separate column with the value of the property in the very next column in the same line. This can be done with the following lines of code:

//if setTitelAsLable is set, take out the property value 
if ((modifiers != null) && ((pos = modifiers.indexOf(IPropertyModifierName.USE_TITLE_AS_LABEL.toLowerCase())) >= 0)) {
modifiers = modifiers.substring(0, pos) + modifiers.substring(pos + IPropertyModifierName.USE_TITLE_AS_LABEL.length());
renderer.getParameters().setParameter(IResourceRendererParameterNameConst.PROPERTY_MODIFIERS, modifiers);
}

//set the properties that are links
contentLink = aPropertyWithModifiers.isModifierSet(IPropertyModifierName.CONTENT_LINK);
comp = renderer.renderMetaProperty(metaName, contentLink);
// render the label only when the property component is not empty
emptyComponent = (comp == null);

if ((comp != null) && comp instanceof MetaPropertyComponent) {
emptyComponent = emptyComponent && ((MetaPropertyComponent) comp).isEmptyComponent();
}

//if setTitleAsLable modifier render the property name
if (!emptyComponent && (aPropertyWithModifiers != null) && aPropertyWithModifiers.isModifierSet(IPropertyModifierName.USE_TITLE_AS_LABEL)) {
TextView textView = new TextView();
textView.setText(metaName.getLabel(res.getContext().getLocale()) + ": ");
textView.setDesign(renderer.getParameters().getParameter(IResourceRendererParameterNameConst.SECONDARYTEXTSTYLE, TextViewDesign.STANDARD));
textView.setWrapping(false);
cell = form.addComponent(row, col, textView);

//now render the property value too
col++;
cell = form.addComponent(row, col, comp);
if (alternatingStyle) {
cell.setStyle(classStyle);
}
//return to the same position you started from
row++;
col--;
}

In any other cases, if the component is not null you can just add the component at the position decided.

cell = form.addComponent(row-1, col+1, EmptyHtmlFragment.render());

Once you have finished rendering a property, you have to check if the current column plus column span has reached the maximum number of columns for a resource. If not, you have to fill the remaining empty space with empty components (or empty images). This is important for the alternating style where, if you do not do this, you will have visible empty spaces within the colored space of a resource. In order to perform this check, you have to distinguish between several cases: check whether the property was rendered in a row with a different cell with a row span, whether the cell is in the starting row of a resource, whether there is a cell with vertical span, or whether there is a cell with a row span but for which the row span is smaller than the current row. In all these cases, the above mentioned check is performed differently due to the dynamic character of the rendering process:

//check whether there was any incomplete cell from the configuration
//currentPosition holds the last column in the root grid that was filled on this row before the current property
int currentPosition = 0;
//if the position is the first row of the resource if (((row-1) == rowWithSpan) || ((row-1 != rowWithSpan) && (row-1 == startingRow))) {
currentPosition = resource*max;
if (columnSpan <= 1)
{
//check whether the property is in the same column as a property with useTitleAsLable modifier, and increase the column span
if (checkTitle(propertyCell, propertyCell[k]))
{
col = col+titleColumns.size();
}
if ((col-currentPosition == getColumnsPerRow(propertyCell, row - startingRow)) && (getColumnsPerRow(propertyCell, row - startingRow) < getColumnsPerResource(propertyCell)))
{
int cout=0;
for (int g = getColumnsPerRow(propertyCell, row - startingRow); g<getColumnsPerResource(propertyCell); g++){
cell = form.addComponent(row - 1, col+ 1, getImageString("20px", res));
col++;
cout++;
if (alternatingStyle)
{
cell.setStyle(classStyle);
}
}
col = col -cout;
}
if (checkTitle(propertyCell, propertyCell[k])){
col = col-titleColumns.size();
}
}else{
if ((col +columnSpan -1 -currentPosition == getColumnsPerRow(propertyCell, row - startingRow)) && (getColumnsPerRow(propertyCell, row - startingRow) < getColumnsPerResource(propertyCell)))
{
int cout = 0;
for (int g = getColumnsPerRow(propertyCell, row - startingRow); g<getColumnsPerResource(propertyCell); g++){
cell = form.addComponent(row - 1, col+columnSpan -1+ 1, getImageString("20px", res));//EmptyHtmlFragment.render());
col++;
cout++;
if (alternatingStyle) {
cell.setStyle(classStyle);
}
}
col = col -cout;
}
}
//check for empty columns before
}else{
//when there is no span
if (((rowWithSpan == 0) && ((row-1) != startingRow)) || ((row != rowWithSpan) && (spanBefore+startingRow-1 < row-1)&&(rowWithSpan!=0))){
currentPosition = resource*max - nbOfSpaces;
if (columnSpan <= 1)
{
if (checkTitle(propertyCell, propertyCell[k]))
{
col = col+titleColumns.size();
}
if ((col-currentPosition == getColumnsPerRow(propertyCell, row - startingRow)) && (getColumnsPerRow(propertyCell, row - startingRow) < getColumnsPerResource(propertyCell)))
{
int cout = 0;
for (int g = getColumnsPerRow(propertyCell, row - startingRow); g<getColumnsPerResource(propertyCell); g++){
cell = form.addComponent(row - 1, col+ 1, getImageString("20px", res));
col++;
cout++;
if (alternatingStyle)
{
cell.setStyle(classStyle);
}
}
col = col - cout;
}
if (checkTitle(propertyCell, propertyCell[k]))
{
col = col-titleColumns.size();
}
}else{
if ((col +columnSpan -1 -currentPosition == getColumnsPerRow(propertyCell, row - startingRow)) && (getColumnsPerRow(propertyCell, row - startingRow) < getColumnsPerResource(propertyCell)))
{
int cout = 0;
for (int g = getColumnsPerRow(propertyCell, row - startingRow); g<getColumnsPerResource(propertyCell); g++){
cell = form.addComponent(row - 1, col+columnSpan -1+ 1, getImageString("20px", res));//EmptyHtmlFragment.render());
col++;
cout++;
if (alternatingStyle) {
cell.setStyle(classStyle);
}
}
col = col-cout;
}
}
}
else{
currentPosition = (resource * (max - 1)) - nbOfSpaces;
if (columnSpan <= 1){
if (checkTitle(propertyCell, propertyCell[k]))
{
col = col+titleColumns.size();
}
if ((col-currentPosition == getColumnsPerRow(propertyCell, row - startingRow)) && (getColumnsPerRow(propertyCell, row - startingRow) < (getColumnsPerResource(propertyCell)-1))) {
int cout = 0;
for (int g = getColumnsPerRow(propertyCell, row - startingRow); g<getColumnsPerResource(propertyCell)-1; g++){
cell = form.addComponent(row - 1, col+ 1, getImageString("20px", res));//EmptyHtmlFragment.render());
col++;
cout++;
if (alternatingStyle) {
cell.setStyle(classStyle);
}
col = col - cout;
}
if (checkTitle(propertyCell, propertyCell[k]))
{
col = col-titleColumns.size();
}
}
else{
if ((col +columnSpan -1 -currentPosition == getColumnsPerRow(propertyCell, row - startingRow)) && (getColumnsPerRow(propertyCell, row - startingRow) < (getColumnsPerResource(propertyCell)-1))) {
int cout = 0;
for (int g = getColumnsPerRow(propertyCell, row - startingRow); g<getColumnsPerResource(propertyCell)-1; g++){
cell = form.addComponent(row - 1, col+columnSpan -1+ 1, getImageString("20px", res));//EmptyHtmlFragment.render()); col++;
cout++;
}
col = col - cout;
}
}
}
if ((((row-1) == rowWithSpan) && (rowSpan > 1)) || ((row != rowWithSpan) && (spanBefore+startingRow-1 < row-1))||(rowWithSpan==0)){
if ((col - currentPosition>1) && (form.getRow(row-1).getCell(col-1).getContent() == null)){
cell = form.addComponent(row - 1, col- 1, getImageString("20px", res));
if (alternatingStyle)
{
cell.setStyle(classStyle);
}
}
}

The method checkTitle, which checks whether there is a component with the modifier useTitleAsLabel in the same column, can be implemented in the following way:

      /**
* checks whether the useTitleAsTitle modifier is set for a component at a specific position.
* @param prop
* @param column
* @return
*/
public boolean checkTitle(IPropertyColumn[] prop, IPropertyColumn property) {
boolean result = false;
IPropertyPosition position = null;
int positionCol = 0;
int positionRow = 0;
for (int k = 0; k < prop.length; k++) {
if (prop[k].equals(property)){
//between two commas
IPropertyWithModifiersList propertyWithModifiers = prop[k].getPropertyWithModifier();
IPropertyWithModifiersListIterator iter = propertyWithModifiers.listIterator();
position = PropertyColumnFactory.getInstance().getPosition(prop[k]);
if (position != null) {
positionCol = position.getColumn();
positionRow = position.getRow();
}
while (iter.hasNext()) {
IPropertyWithModifiers aPropertyWithModifiers = iter.next();

if ((prop[k].equals(property))&& (aPropertyWithModifiers != null)
&& aPropertyWithModifiers.isModifierSet(IPropertyModifierName.USE_TITLE_AS_LABEL)) {
result = true;
}
}
}
}
return result;
}

Moreover, if the useTitleAsLable modifier is set, all the other properties rendered in the same column and different rows must have a column span increased by 1, while the column position of the properties situated after such a property will also be increased by 1. The following lines of code show this:

if (row == rowWithSpan)
{
if (titleColumns.containsKey(new Integer(positionCol-1)) && (!checkTitle(propertyCell, propertyCell[k])))
{
columnSpan++;
if (columnSpan == 1)
columnSpan++;
}
else{
for (int h = 1; h<(positionCol-1); h++)
{
if (titleColumns.containsKey(new Integer(h)))
col++;
}
}
}
else{
if (titleColumns.containsKey(new Integer(positionCol)) && (!checkTitle(propertyCell, propertyCell[k])))
{
columnSpan++;
if (columnSpan == 1)
columnSpan++;
}
else{
for (int h = 1; h<(positionCol); h++)
{
if (titleColumns.containsKey(new Integer(h)))
col++;
}
}
}

Once you have finished rendering a resource, you have to add the necessary spaces for separating resources and prepare your input variables for the next resource. You have to treat the case when the resource is the last resource in the row of resources separately, because in this case you have to move to a new row of resources and so you do not add any resource separators and you change your global variables.

1) The resource is not the last one in the row of resources

At this point, you have to consider the row background style set in the configuration.

cell = form.addComponent(startingRow, (max) * resource, getImageString(cSpace, res));

In the code above, startingRow represents the first row where this resource was rendered. (max) * resource is the column immediately to the left of the resource. cSpace represents the column space set in the portal configuration.

          if (lineSeparatorStyle) {
//separate the cases if the resource is the first one and if it is not
if (resource == 1) {
//now add the separator rows
//work from the first column to the last column before the vertical line(one column past the end of the resource)
for (int cc = 1; cc <= (((max) * resource) - 2); cc++) {
//if it is not the last row
if ((gridRow != this.numberOfRows) && (gridRow != nbRows)) { //(i < (renderlist.size() - this.numberOfColumns))
//add space+line+slace
cell = form.addComponent(maxRow, cc, getImageStringRow(getHalfSpacing(rSpace), res));
cell = form.addComponent(maxRow + 1, cc, HtmlRendererUtil.renderLineSeparator(HtmlRendererUtil.CLASS_STYLE_LINE, 2));
cell = form.addComponent(maxRow + 2, cc, getImageStringRow(getHalfSpacing(rSpace), res));
}
}
} else if (resource > 1) {
//in this case start from position 0 (one column before the begining of the resource)
//and do the same
for (int z = 0; z <= (max - 2); z++) {
if ((gridRow != (this.numberOfRows)) && (gridRow != nbRows)) {
cell = form.addComponent(maxRow, (((resource - 1) * max) + z) - (nbOfSpaces / 3), getImageStringRow(getHalfSpacing(rSpace), res));
cell = form.addComponent(maxRow + 1, (((resource - 1) * max) + z) - (nbOfSpaces / 3),
HtmlRendererUtil.renderLineSeparator(HtmlRendererUtil.CLASS_STYLE_LINE, 2));
cell = form.addComponent(maxRow + 2, (((resource - 1) * max) + z) - (nbOfSpaces / 3), getImageStringRow(getHalfSpacing(rSpace), res));
}
}
}

//if it is not the last resource to be rendered
//place a the vertical separating line
if (i < (renderlist.size() - 1)) {
//add space+vertical line+space
cell = form.addComponent(startingRow, ((max) * resource) - 2, getImageString(getHalfSpacing(cSpace), res));
cell.setRowspan(maxNumberOfRows);

//add the separating line
cell = form.addComponent(startingRow, ((max) * resource) - 1, HtmlRendererUtil.renderVerticalLineSeparator(HtmlRendererUtil.CLASS_STYLE_LINE, 2));
cell.setWidth(";height:100%");
//if it is the row before the last row, and it is blank below, draw the line to the end of the grid
if ( (gridRow == (nbRows%this.numberOfRows)-1)&&((renderlist.size()%(this.numberOfColumns))!= 0)&&
(resource>=(renderlist.size()%(this.numberOfColumns)))&&(resource >= 1))
{//HERE(i >= (renderlist.size() - this.numberOfColumns))
cell.setRowspan((2 * maxNumberOfRows) + 3);
} else {
cell.setRowspan(maxNumberOfRows + 3);
}

//add another space
cell = form.addComponent(startingRow, (max) * resource, getImageString(getHalfSpacing(cSpace), res));
cell.setRowspan(maxNumberOfRows);
nbOfSpaces = nbOfSpaces + 3;
}
}

If you want to present the resources in alternating style, you will need to make the transition between the two colors of two neighboring resources right in the middle of the separation space. Therefore, you will have two color half of the separating image in one color and the other half in the other color. We manage this by setting two images with the length of half the separator space, each colored differently.

if (alternatingStyle) {
cell = form.addComponent(startingRow, ((max) * resource) - 1, getImageString(getHalfSpacing(cSpace), res));
cell.setRowspan(maxNumberOfRows);
cell.setStyle(classStyleTemp);
nbOfSpaces++;

//the second space is added only if it is not the last resource
if (i < (renderlist.size() - 1)) {
cell = form.addComponent(startingRow, (max) * resource, getImageString(getHalfSpacing(cSpace), res));
cell.setRowspan(maxNumberOfRows);
cell.setStyle(classStyle);
nbOfSpaces++;
}

//for the first row, add spaces to make the grid look symmetrical
if (resource == 1) {
if (gridRow == 1) {
for (int z = 1; z < max; z++) {
cell = form.addComponent(2, ((resource - 1) * max) + z, getImageString(getHalfSpacing(rSpace), res));
cell.setStyle(classStyleTemp);
}
}

//now add the empty rows colored appropriately
for (int cc = 1; cc <= (((max) * resource) - 1); cc++) {
cell = form.addComponent(maxRow, cc, getImageString(getHalfSpacing(rSpace), res));
cell.setStyle(classStyleTemp);

if ((gridRow != this.numberOfRows) && (i < (renderlist.size() - this.numberOfColumns))) {
cell = form.addComponent(maxRow + 1, cc, getImageString(getHalfSpacing(rSpace), res));
cell.setStyle(classStyle);
}
}
} else if (resource > 1) {
//now add the space between the resources
//now we add two images (one colored as the previous resource and the other as the next resource),
// each with a length of half the separating space.
//half blue and half white
if (gridRow == 1) {
for (int z = 0; z < max; z++) {
cell = form.addComponent(2, ((resource - 1) * max) + z, getImageString(getHalfSpacing(rSpace), res));
cell.setStyle(classStyleTemp);
}
}

for (int cc = max * (resource - 1); cc <= (((max) * resource) - 1); cc++) {
cell = form.addComponent(maxRow, cc, getImageString(getHalfSpacing(rSpace), res));
cell.setStyle(classStyleTemp);

if ((gridRow != this.numberOfRows) && (i < (renderlist.size() - this.numberOfColumns))) {
cell = form.addComponent(maxRow + 1, cc, getImageString(getHalfSpacing(rSpace), res));
cell.setStyle(classStyle);
}
}
}}

The method getHalfSpacing takes the column space or row space set in the configuration as a parameter:

private String getHalfSpacing(String spacing) {
try {
int space = Integer.parseInt(spacing);

if (space == 0) {
return spacing;
} else {
return String.valueOf(space / 2);
}
} catch (NumberFormatException nfEx) {
log.debugT("Could not get an integer from <" + spacing + ">; " + LoggingFormatter.extractCallstack(nfEx));
}

return spacing;
}


2) The resource is the last one in the row of resources
(resource + 1) == this.numberOfColumns
In this case, you have to provide horizontal separators between rows of resources. The example below shows the lines of codes used for alternating style:
if (alternatingStyle) {
//if not at the last resource, add the separators between the last resource in that row and in the previous one
if (i <= (renderlist.size() - 1)) {
cell = form.addComponent(startingRow, ((max) * (resource + 1)) - 1,
getImageString(getHalfSpacing(cSpace), res));
cell.setRowspan(maxNumberOfRows);
cell.setStyle(classStyleTemp);
nbOfSpaces++;
}

//also add the separator rows at the begining of the collection for symmetry
if (gridRow == 2) {
if (resource > 0) {
for (int z = 0; z < max; z++) {
if (resource == 0) {
cell = form.addComponent(2, z + 1, getImageString(getHalfSpacing(rSpace), res));
} else {
cell = form.addComponent(2, ((resource) * max) + z, getImageString(getHalfSpacing(rSpace), res));
}

cell.setStyle(classStyleTemp);
}
} else {
for (int z = 0; z < (max - 1); z++) {
if (resource == 0) {
cell = form.addComponent(2, z + 1, getImageString(getHalfSpacing(rSpace), res));
} else {
cell = form.addComponent(2, ((resource) * max) + z, getImageString(getHalfSpacing(rSpace), res));
}

cell.setStyle(classStyleTemp);
}
}
}

//always differentiate between when the resource is 0 and when it is not, since if it is 0
//we cannot use this value to determine the position of the separators
if (resource > 0) {
for (int cc = max * (resource); cc <= (((max) * (resource + 1)) - 1); cc++) {
cell = form.addComponent(maxRow, cc, getImageString(getHalfSpacing(rSpace), res));
cell.setStyle(classStyleTemp);

if (i < (renderlist.size() - this.numberOfColumns)) {
cell = form.addComponent(maxRow + 1, cc, getImageString(getHalfSpacing(rSpace), res));

if ((this.numberOfColumns % 2) == 0) {
if (classStyle == HtmlRendererUtil.CLASS_STYLE_DARK) {
cell.setStyle(HtmlRendererUtil.CLASS_STYLE_LIGHT);
} else {
cell.setStyle(HtmlRendererUtil.CLASS_STYLE_DARK);
}
} else {
cell.setStyle(classStyle);
}
}
}
} else {
for (int cc = 1; cc <= (((max) * (resource + 1)) - 1); cc++) {
cell = form.addComponent(maxRow, cc, getImageString(getHalfSpacing(rSpace), res));
cell.setStyle(classStyleTemp);

if ((gridRow != (this.numberOfRows + 1)) && (i < (renderlist.size() - this.numberOfColumns))) {
cell = form.addComponent(maxRow + 1, cc, getImageString(getHalfSpacing(rSpace), res));

if ((this.numberOfColumns % 2) == 0) {
if (classStyle == HtmlRendererUtil.CLASS_STYLE_DARK) {
cell.setStyle(HtmlRendererUtil.CLASS_STYLE_LIGHT);
} else {
cell.setStyle(HtmlRendererUtil.CLASS_STYLE_DARK);
}
} else {
cell.setStyle(classStyle);
}
}
}
}
}

Finally, you can start rendering a new row of resources. The code below shows how the variables are initialized for a new row of resources:
col = 1;
resource = 0;
span = 0;

if (alternatingStyle) {
startingRow = maxRow + 2;
} else {
if (lineSeparatorStyle) {
startingRow = maxRow + 3;
} else {
startingRow = maxRow + 1;
}
}

nbOfSpaces = 0;

Here is how the expected result should look like for the three different styles:

Application

The functionality of this collection renderer is exemplified below. This collection renderer can be used to display search results instead of the list collection renderer. However, it adds more flexibility to the manner in which the resources are displayed. Whereas with the list collection renderer you could only display the search results in a list, with this method you have the freedom to choose the number of columns in which you want to display your results. Of course, choosing columns = 1, will give the same result as with the list collection renderer. Below you can see how the search results are rendered in two columns:

Resource Renderer

Introduction

A resource renderer is responsible for displaying a resource (file, folder, or link) on the screen.

Choose one of the following options:

com.sapportals.wcm.rendering.resource.IResourceRenderer

com.sapportals.wcm.rendering.resource.AbstractResourceRenderer

Overwrite at least the following methods of AbstractResourceRenderer:

render() (with different signatures):
This method is called by the framework to perform the rendering action

getNewInstance(): This method returns a new instance of the resource renderer

Coding in Detail
public class MyResourceRenderer extends AbstractResourceRenderer {

public ILayoutObject getNewInstance() {
returns new MyResourceRenderer();
}
public Component render() throws WcmException{
renderLink(this.getResource());
}
public Component render(IProperty property) throws WcmException {
// renders a single property
}
public Component renderIcon() throws WcmException {
// renders an icon representation
}
public Component renderImage() throws WcmException {
// renders an image
}

//custom method
private Component renderLink(IResource res){
TextView tv = new TextView();
tv.setText(res.getName());
returns this.renderContentLink(res, tv);
}
}

Property Renderer

Introduction

A property renderer is responsible for displaying the properties of a resource in the Navigation iView. The property renderer is called by the resource renderer when a property of the resource is to be displayed.

com.sapportals.wcm.rendering.property.IModelledPropertyRenderer

Note that this interface is valid only for display in the Navigation iView, and not for properties control.

renderProperty(IProperty property, IMetaName metaName, IResource resource, IProxy proxy, IParameters parameters):

This method is called by the resource renderer when the rendering action is needed. Here are

some details on how to implement this method:

public Component renderProperty(IProperty property, IMetaName metaName, IResource resource, IProxy proxy, IParameters parameters) throws WcmException {
// renders a property
}

Parameters

Examples

Some examples of property renderers are explained here. These examples are presented in order of difficulty, in order to show how the development evolves from a simple to a more complex property renderer application.

Example 1: Permission Renderer

Setup

The first example presents the basic property renderer implementation. The task in this example is to display (in the Navigation iView) the permissions the user has for a given resource. For example, if the user is the owner of the resource, an icon is displayed in the permission column signaling the fact that this user has full control over this resource. On the other hand, if the user is not the owner of the resource, a different icon is displayed showing that the user is not the owner. Since a permission can be inherited from the parent directory, we also want to use special icons to show, for each permission type, whether the permission was inherited or not. In either case, the icon displayed has to have a tooltip stating the type of permission: "owner" in the first case, or any other permission status, such as write permission, in the latter case. To differentiate all the possible cases you need a total of four icons:

- icon for an owner permission.

- icon for a normal permission.

- icon for an inherited normal permission.

- icon for an inherited owner permission.

The standard, predefined permissions are:

Expected outcome

You can see the expected outcome for the four cases below:

To better show the inheritance relation of the permission property, here is a screenshot of the documents displayed with a collection tree renderer:

You can see that the folder AGA has an owner permission, and while its subfolders sub1 and sub2 have inherited this permission. Also the folder xmlforms has a permission created by someone other than the owner and the subfolder xtest has inherited this permission.

Requirements

The type of the property (that is, permission) and the resource for which it is set are given using the method parameters. The names of the icons to be displayed for the permissions are private, static, final, and global variables of the renderer class. The first condition needed to be able to render this property is that the resource for which it is called exists. Also, if no permission is stated for the current user for a given resource, the property is not displayed.

In order to perform this task you need an IResourceAclManager (com.sapportals.wcm.repository.security.IResourceAclManager) to manage the permissions for a given resource.

The terms 'ACL' and 'permissions' are used here almost interchangeably.

Background Information

To make method calls clearer and to understand how different objects that are relevant for permissions use each other, a brief explanation is necessary. IResourceAclManagers are used by IAclSecurityManagers. IResourceAclManagers use IAclManagers in a one-to-one mapping. IResourceAclManagersoperate on IResources. IAclManagers operate on arbitrary objects that are identified by a unique ID. IResourceAclManagerspass the URI of the IResources as object IDs to the IAclManagers. IResourceAclManagers pass the logged-on user (from the resource context) as the 'caller' to IAclManagers. IResourceAclManagersuse the wrappers IResourceAcl (for IAcl), IResourceAclEntry (for IAclEntry) to hide the calls of the IAclManager (so that nobody can pass a fake 'caller'.

Implementation

Going back to the current example, you need to check whether the security manager for the given resource is an ACL security manager. If so, the ACL manager is initialized with the ACL manager from the ACL security manager (this is an IAclSecurityManager object in (com.sapportals.wcm.repository.manager.IAclSecurityManager) of this resource).

At this point, an IResourceAcl (com.sapportals.wcm.repository.security.IResourceAclManager) object can be used to retrieve the ACL for this resource. To retrieve the non-inherited ACLs you should call the method aclManager.getAcl(resource), whereas to retrieve only the inherited ACLs you should call the method aclManager.getInheritedAcl(resource). If the inherited ACL is not empty you have to check whether the user is the owner or has inherited an owner permission. Then, if the ACL is not empty, you firstly have to check whether the current user is the owner for this resource. If this is the case, the owner icon is returned. Otherwise, since the cases when the ACL is inherited were already excluded, the user has a normal permission.

If the permission for the current user is found, the corresponding icon is displayed together with the tooltip stating the permission description. The tooltip for a user other than the owner is set using the permission description available for IResourceAclEntry objects. This is implemented in the method getAclTooltip(IResourceAclresAcl, IUser curUser, IResource resource) of the following java code sample.

Note that a tooltip set in this way is location-dependent and language-dependent (since the method getLocale() is used).

For the owner however, since the property description does not return the status "owner", the language dependent tooltip is more difficult to obtain. To do this, you have to create the file with the extension .properties specifying the key (in this case the tooltip 'Owner' for example) and the value of the key (the string that is to be displayed). Since the key specified is unique, the value of this key is translated into the language of the portal, making the tooltip language-dependent. This functionality is implemented in the method getTooltip(Stringkey, IResource resource)of the following java class.

In order to see the language dependency of the tooltip, you can change the language of your portal by clicking the Personalize button.

The component to be displayed (the icon) should be set as a link so that with one click the permission details are displayed. . To do this, you have to attach the command details_permissions_ex to the relevant icon. This is the command in the configuration for displaying the permission details. This feature in implemented in the method getComponent(IResourceresource, IProxy proxy, String imagePath, String tooltip) by these lines of code:

UICommandRenderer uir = new UICommandRenderer(new LayoutObject(), proxy);
Component comp = uir.renderCommandContentLink(resource,
"details_permissions_ex", icon);

The following code sample shows the implementation of the renderProperty method, which executes all the functionality explained above and the additional helping methods.

public Component renderProperty(IProperty property, IMetaName metaName, IResource resource, IProxy proxy, IParameters parameters) throws WcmException {
try {
//object that manages the permissions
IResourceAclManager aclManager = null;

//if the security manager for this resource is an ACL (Access Control Lists) security manager
if (resource.getRepositoryManager().getSecurityManager(null) instanceof IAclSecurityManager) {
IAclSecurityManager securitymanager = (IAclSecurityManager) resource.getRepositoryManager().getSecurityManager(null);
//gets the permissions manager from the security manager of this resource
aclManager = securitymanager.getAclManager();
}
//gets the access control list for this resource
IResourceAcl resAcl = aclManager.getAcl(resource);
//gets the access control list inherited for this resource
IResourceAcl resAclInherited = aclManager.getInheritedAcl(resource);
//gets the current user
IUser curUser = resource.getContext().getUser();
//if the list of permissions is not empty
//Inheritance
if (resAclInherited != null) {
//Checks owner
IUMPrincipalList aclList = resAclInherited.getOwners();

//is owner
if (this.isQwner(aclList, curUser)) {
return getComponent(resource, proxy, this.IMAGE_OWNER_INHERITED, getTooltip(this.OWNER_KEY, resource));
}
//has acldad
else {
return getComponent(resource, proxy, this.IMAGE_USER_INHERITED,
getAclTooltip(resAclInherited, curUser, resource));
}
}
//has acls
else if (resAcl != null) {
//Check owner
IUMPrincipalList aclList = resAcl.getOwners();

//is owner
if (this.isQwner(aclList, curUser)) {
return getComponent(resource, proxy, this.IMAGE_OWNER, getTooltip(this.OWNER_KEY, resource));
}
//has acldad
else {
return getComponent(resource, proxy, this.IMAGE_USER, getAclTooltip(resAcl, curUser, resource));
}
} else {
return EmptyHtmlFragment.render();
}

//return null;
} catch (WcmException wcmEx) {
log.errorT("Could not check property availability for resource < " + resource + " > " +
LoggingFormatter.extractCallstack(wcmEx));
}

return EmptyHtmlFragment.render();

//returns null;
}

/**
* Tests whether the found IUMPrincipal is the current user or a group or a role that includes
that user
*/
private boolean userFound(IUMPrincipal aclPrincipal, IUser currentUser) {
if ((aclPrincipal == null) && (currentUser == null)) {
return false;
}

if (IUMPrincipal.IUSER == aclPrincipal.getType()) {
return currentUser.equals(aclPrincipal);
} else if (IUMPrincipal.IGROUP == aclPrincipal.getType()) {
String groupID = aclPrincipal.getId();

return currentUser.isInGroup(groupID);
} else if (IUMPrincipal.IROLE == aclPrincipal.getType()) {
String roleID = aclPrincipal.getId();

return currentUser.isInRole(roleID);
}

return false;
}

/**
* checks whether a given user is the owner of a resource
*/
private boolean isQwner(IUMPrincipalList aclPrincipalList, IUser currentUser) {
if ((aclPrincipalList == null) || (currentUser == null)) {
return false;
}

IUMPrincipalListIterator iter = aclPrincipalList.iterator();
boolean isOwner = false;

while (iter.hasNext()) {
isOwner = userFound(iter.next(), currentUser);

if (isOwner) {
break;
}
}

return isOwner;
}

/**
* gets the link image component to be displayed
*/
private Component getComponent(IResource resource, IProxy proxy, String imagePath, String tooltip) {
try {
Image icon = URLGeneratorHelper.getImage(imagePath, "", null);
icon.setWidth("16");
icon.setHeight("16");

if (tooltip != null) {
icon.setTooltip(tooltip);
}

UICommandRenderer uir = new UICommandRenderer(new LayoutObject(), proxy);
Component comp = uir.renderCommandContentLink(resource, "details_permissions_ex", icon);

//todo: set the tooltip
return comp;
} catch (WcmException wcmEx) {
log.errorT("Could not check property availability for resource < " + resource + " > " +
LoggingFormatter.extractCallstack(wcmEx));
}

return EmptyHtmlFragment.render();
}

/**
* gets the tooltip for the permission property if when the user is the owner/inherited owner
*/
private String getTooltip(String key, IResource resource) {
try {
//takes the Owner tooltip from the properties file
ResourceBundles bundle = ResourceBundles.getBundle(PROPERTY_RENDERER_BUNDLE_FILE);

//set the tooltip for the icon
//the resource bundle object searches for the .properties file first with the language specification, eg. *_de.peroperties
//and then just for the general .properties file, *.properties
return bundle.getString(key, resource.getContext().getLocale());
} catch (MissingResourceException mrEx) {
log.errorT("Could not find the bundle file; no tooltip " + LoggingFormatter.extractCallstack(mrEx));
}
return null;
}

/**
* gets the tooltip for a normal ACL (not the owner)
*/
private String getAclTooltip(IResourceAcl resAcl, IUser curUser, IResource resource) {
String tooltip = null;
try {
//gets the permissions entries
IResourceAclEntryList permissions = resAcl.getEntries();

//iterates the list
IResourceAclEntryListIterator iter = permissions.iterator();

while (iter.hasNext()) {
//gets a single permision
IResourceAclEntry permission = iter.next();

//finds the permission for the current user and renders it
if (userFound(permission.getPrincipal(), curUser)) {
//gets the description of the permission (ex: read, werite etc.) and sets it as a tooltip
tooltip = permission.getPermission().getDescription(resource.getContext().getLocale());
break;
}
}
} catch (Exception ex) {
log.errorT("Could not find the bundle file; no tooltip " + LoggingFormatter.extractCallstack(ex));
}

return tooltip;
}
}

In order to see the permission icons, you need to deploy the project and register the class in the configuration. This is explained later on in section 2.

Example 2: Subscription Renderer

Setup

The second example is a bit more complicated. The task is, in the first phase, to display (in the Navigation iView) the subscriptions that exist for each resource. The subscription property is indicated by an icon in the menu bar. If a subscription exists for a given resource, an icon is to be displayed in the subscription column for the resource in question. A tooltip also has to be set for the icon that indicates the type of the subscription (daily, weekly, and so on).

In order to display this property, you need a property renderer for the subscription. The type of the property (subscription), and the resource for which it is set are given through the method parameters. The name of the icon to be displayed for the subscription is a private, static, final, global variable of the renderer class. Like in example 1 above, the first condition for being able to render this property is that the resource for which it is called exists.

Expected Outcome

This is a screenshot of the expected result in the Navigation iView:

Requirements

The property is only displayed if a subscription actually exists for a given resource. It is not displayed permanently. In order to obtain the subscriptions, you can use an ISubscriptionManager (com.sapportals.wcm.repository.service.subscription.ISubscriptionList) object that creates, manages, and returns subscriptions.

Implementation

First and foremost, you have to be sure that the resource for which the property rendering is carried out exists. Therefore, if the manager is not empty, the list of subscriptions for the given resource is obtained from the subscription manager as an ISubscriptionList object (com.sapportals.wcm.repository.service.subscription.ISubscriptionList).

This list can be iterated using the ISubscriptionListIterator (com.sapportals.wcm.repository.service.subscription.ISubscriptionList. At this point, all that is left to do is to set an icon and a tooltip for the subscriptions in the list. This icon is then returned. If the list of subscriptions is empty, null is returned.

Below is the renderProperty method that carries out practically all of the functionality explained above.

public Component renderProperty(IProperty property, IMetaName metaName, IResource resource, IProxy proxy, IParameters parameters) throws WcmException {
try
{
//object that manages the subscription
ISubscriptionManager manager = (ISubscriptionManager) ResourceFactory.getInstance().getServiceFactory().
getRepositoryService(resource,IWcmConst.SUBSCRIPTION_SERVICE);
//if there are some subscriptions then start rendering //get the list of subscriptions from the subscription manager if (manager != null) { //get the list of subscriptions for the given resource from the subscription manager ISubscriptionList subscriptions = manager.getSubscriptions(resource.getContext(), resource.getContext().getUser().getId(), resource); //if the list of subscriptions is not empty then use an iterator to get the subscriptions if ( !subscriptions.isEmpty() ) { ISubscription subscription = subscriptions.listIterator().next(); //set the icon for the subscription Image icon = URLGeneratorHelper.getImage(IMAGE, "", null); icon.setWidth("16"); icon.setHeight("16"); //set the tooltip for the icon icon.setTooltip(subscription.getCondition().getInterval().getDescription(resource.getContext().getLocale())); return icon; } //if the list of subscriptions is empty else { //render an empty component return EmptyHtmlFragment.render(); } } } catch (WcmException wcmEx) { log.errorT("Could not check property availability for resource < " + resource + " > " + LoggingFormatter.extractCallstack(wcmEx)); } //render an empty component return EmptyHtmlFragment.render(); }

Note that the tooltip set in the method above is location-dependent and language-dependent (since the method getLocale() is used).

As for example 1 above, in order to see the subscription you need to deploy the class, register the renderer in the configuration, and create the property. The procedure for doing this is explained later in section 2.

Enhancement

This is a more complicated example that combines the resource renderer described above with UI commands functionality. The task is to attach to the subscription icon a hover menu with the possible UI command for the given subscription, to be displayed when the icon is clicked. One should also differentiate between subscriptions made by the user for himself and subscriptions made by somebody else for this user. Therefore, our hover menu can have only one command: Edit subscription for a subscription made by the user for himself and Unsubscribe, for the subscriptions made by someone else for the user (these are the only available commands for the respective cases) or two commands (both Edit subscription and Unsubscribe) when there is both a subscription made by the user for himself and by someone else for the same resource. Clicking one command from the hover menu allows you to edit the subscription or to unsubscribe respectively.

The following screenshots show what the expected result should look like for the three cases:

If the Subscription command is available, you can click on it to edit the subscription:

Implementation

In order to achieve this task, your render Property method should look like this:

   /* method that renders the subscription
* @see com.sapportals.wcm.rendering.property.IModelledPropertyRenderer#renderProperty(com.sapportals.wcm.repository.IProperty, com.sapportals.wcm.service.propertyconfig.IMetaName, com.sapportals.wcm.repository.IResource, com.sapportals.wcm.rendering.base.IProxy, com.sapportals.wcm.repository.service.layout.customizing.IParameters)
* property is null as it is not permanent
* metaName = subscription
* resource is the respective document for which the subscription is rendered
* proxy is the frame
*/
public Component renderProperty(IProperty property, IMetaName metaName,
IResource resource, IProxy proxy, IParameters parameters)
throws WcmException {
try {
//object that manages the subscription
ISubscriptionManager manager = (ISubscriptionManager) ResourceFactory.getInstance()
.getServiceFactory()
.getRepositoryService(resource,
IWcmConst.SUBSCRIPTION_SERVICE);

//if there are some subscriptions then render
if (manager != null) {
//this getsubscriptions method gets all the subscriptions owned by the user
ISubscriptionList subscriptions = manager.getSubscriptions(resource.getContext(),
resource.getContext().getUser().getId(), resource);
RecipientFactory rf = RecipientFactory.getInstance();
//this getSubscriptions method gets all the subscriptions received by the user
ISubscriptionList subscriptionsRec = manager.getSubscriptions(resource.getContext(),
rf.getRecipient(resource.getContext().getUser()), true);

//if the list of subscriptions owned or only received is not empty, iterate the subscriptions
LinkedList subList = new LinkedList();

if (!subscriptionsRec.isEmpty() && (subscriptionsRec != null)) {
ISubscriptionListIterator iter = subscriptionsRec.listIterator();

while (iter.hasNext()) {
ISubscription subscription = iter.next();
//take out those subscriptions made for the current resource
IResourceList resourceList = subscription.getResources();

if (resourceList.containsResource(resource)) {
subList.add(subscription);
}
}
}
if ((subList != null) && !subList.isEmpty()) {
//render Icon
Image icon = URLGeneratorHelper.getImage(IMAGE, "", null);
icon.setWidth("16");
icon.setHeight("16");
//get a hover menu object
HoverMenu menuAdvanced = new HoverMenu(proxy.createDispatchableID(AbstractProxyControl.ON_EXECUTE, new UUID().toString()));
menuAdvanced.setMenuTrigger(HoverMenuTrigger.ONLRCLICK);
menuAdvanced.setFirstLevelVisible(false);
menuAdvanced.setOnHoverMenuClick(AbstractProxyControl.DISPATCH_EVENT);

//the UI command
IUIBaseCommand baseCommand = null;
IExecCommand command = null;
String label = null;
//an item of the menu that would be displayed; typlically contains a command
HoverMenuItem hoverItem = null;
LinkAttributes linkAttributes = null;
UICommandFactory factory = UICommandFactory.getInstance();

for (int s = 0; s < subList.size(); s++) {
ISubscription subscription = (ISubscription) subList.get(s);
//set the tooltip
icon.setTooltip(subscription.getCondition().getInterval()
.getDescription(resource.getContext()
.getLocale()));
//if the subscription is included in those made by the user for himself
//then the possible command is Edit subscription
if (subscriptions.contains(subscription)) {
baseCommand = factory.getCommandByAlias("subscription",
resource.getContext());
//check the validity and usability of this command
if ((baseCommand != null) &&
(baseCommand instanceof IExecCommand)) {
command = (IExecCommand) baseCommand;
command.setResource(resource);

if (command.isApplicable() &&
command.isExecutable()) {
label = command.getText(TextKey.LABEL);
hoverItem = new HoverMenuItem(createTarget(
command, proxy), label);
if (!command.raisesEvent()) {
linkAttributes = command.getLinkAttributes();

if (linkAttributes != null) {
if (linkAttributes.isJavaScript()) {
hoverItem.setClientSideScript(linkAttributes.getReference());
} else { hoverItem.setLinkReference(linkAttributes.getReference());
} hoverItem.setLinkTarget(linkAttributes.getTarget());
}
}

menuAdvanced.addMenuItem(hoverItem);
}
}
}
else {
//if the subscription was made by someone else for this user
//then the available command is Unsubscribe
baseCommand = factory.getCommandByAlias("unsubscribe",
resource.getContext()); // check the validity and usability of this command
if ((baseCommand != null) &&
(baseCommand instanceof IExecCommand)) {
command = (IExecCommand) baseCommand;
command.setResource(resource);
if (command.isApplicable()) {
label = command.getText(TextKey.LABEL);
hoverItem = new HoverMenuItem(createTarget(command, proxy), label);
if (!command.raisesEvent()) {
linkAttributes = command.getLinkAttributes();
if (linkAttributes != null) {
if (linkAttributes.isJavaScript()) {
hoverItem.setClientSideScript(linkAttributes.getReference());
} else { hoverItem.setLinkReference(linkAttributes.getReference());
} hoverItem.setLinkTarget(linkAttributes.getTarget());
}
} //add the item (command) to the menu
menuAdvanced.addMenuItem(hoverItem);
}
}
}
}
PopupTrigger pp = new PopupTrigger(IDCounter.currentID(),
menuAdvanced);

//set the icon for the displayed object
pp.setContent(icon);
pp.setInteractive(true);

return pp;
}
//if the list of subscriptions is empty
else {
// render empty comp
return EmptyHtmlFragment.render();
}
}
} catch (WcmException wcmEx) {
log.errorT("Could not check property availability for resource < " +
resource + " > " + LoggingFormatter.extractCallstack(wcmEx));
}
return EmptyHtmlFragment.render();
}

/**
* method that creates a target for a hover menu item
*/
public String createTarget(IUIGroupCommand group, IProxy proxy)
throws WcmException {
try {
if (proxy == null) {
return IDCounter.currentID();
}
ArrayList values = new ArrayList();
values.add(group.getGroupID());
values.add(IDCounter.currentID());
return proxy.createDispatchableID(AbstractProxyControl.ON_EXECUTE,
values);
} catch (ClassCastException cce) {
throw new WcmException(cce);
}
}

/**
* method that creates a target for a hover menu item
*/
public String createTarget(IExecCommand command, IProxy proxy)
throws WcmException {
try {
if (proxy == null) {
return IDCounter.currentID();
}
ArrayList values = UICommandFactory.getInstance()
.createTargetValues(command);
String action = AbstractProxyControl.ON_EXECUTE;
if (command instanceof IMassExecCommand) {
action = AbstractProxyControl.ON_MASS_EXECUTE;
}
return proxy.createDispatchableID(action, addMenuType(values));
} catch (ClassCastException cce) {
throw new WcmException(cce);
}
}

private ArrayList addMenuType(ArrayList list) {
if (list != null) {
int size = list.size();

if ((size > 0) && ((size - 2) >= 0)) {
list.add(size - 2, IUIMenu.HOVER_MENU_TYPE);
}
}
return list;
}
}

Commands

You can use user interface commands to execute operations. Some commands can be applied to multiple resources (mass operations), but others can only be used on individual resources.

Choose one of the following options:

com.sapportals.wcm.rendering.uicommand.ICommand

com.sapportals.wcm.rendering.uicommand.AbstractCommand

Overwrite at least the following methods of AbstractUICommand:

This method is called when the command button is clicked and raisesEvent is true.

Coding in Detail:

public class MyUICommand extends AbstractCommand {

public MyUICommand(){
super("myButtonTextKey", "myTooltipTextKey");
}

public ICommand getNewInstance() {
return this.initNewInstance(new MyUICommand());
}

public IRenderingEvent execute(IScreenflowData arg0) throws WcmException {
return null;
}

public String[] getTargetParameters() throws WcmException {
return new String[0];
}

public void setTargetParameters(List list, IResourceContext context) throws WcmException {
}

public boolean isExecutable() {
return true;
}
}

Resource Bundle

You have two options for handling the texts of your command:

1) Create a resource bundle containing the localized texts
  • Create a bundle file for each language on the portal server
    in your "<project>/dist/PORTAL-INF/private/lib" directory. Use a text editor to do this.
    Note the conventions on the right.

<bundle file name>_<language> .properties

For example,

my_UICConsts_de.properties

my_UICConsts_es.properties

You also need a bundle file for the default language (usually English).

For example,

my_UICConsts.properties

  • In each of these files, create an entry in the format key=label.

Define at least the two properties from the code on the previous page.

"myButtonTextKey" and "myTooltipTextKey"

(You can use another key name, but it has to be the same in the code as in the resource bundle)

For example, test_property=My Label

myButtonTextKey=My Button

myTooltipTextKey=My Button

  • Create a new JAR file (in the same directory) for integrating the new bundle files. Use a packing
    program such as WinZip&#174; to do this. Create a new archive and add the bundle files to it. Save the archive with the extension .jar

For example, my_bundles.jar

If you have set your resource bundle correctly in the command settings (see section 2.7 for more information), the resource bundle is available for the flexible UI framework, and your labels are correctly rendered.

2) Hard Coding the Values

If you do not want to add a resource bundle for the button title, for instance, you can overwrite the following methods.

protected String getLabel() {
   return "myButtonLabel";
      }

      protected String getTooltip() {
            return " myButtonTooltip";
      }

Example - URL Redirect

This example opens a new browser window and loads the specified URL.

Add the following code to your class.

/**
* Prevents an event being fired.
* The execute method will not be called.
*/
public boolean raisesEvent() {
return false;
}

/**
* Causes a redirect to the specified URL
* in the specified target frame.
*/
public LinkAttributes getLinkAttributes() {
return new LinkAttributes("http://www.sap.com", "_blank");
}

Example - Delete Resource This example deletes the command's resource. Add the following code to your class (remove the code from the URL Redirect example!). public boolean raisesEvent() { return true; } public void setTargetParameters(List values, IResourceContext con) throws WcmException { this.values = values; this.context = con; } public String[] getTargetParameters() throws WcmException { String result[] = new String[1]; result[0] = this.resource.getRID().getPath(); return result; } public IRenderingEvent execute(IScreenflowData data) throws WcmException { IResource resource = this.createResource((String)this.values.get(0)); if(resource != null){ resource.delete(); } else { return new RenderingEvent( new CflStatusInfoEvent(StatusType.ERROR, "Resource null!")); } return new RenderingEvent(new CflStatusInfoEvent(StatusType.OK, resource.getName()+" deleted successfully.")); }
Using Parameters

As an example, say we implement a URL redirect UI command that causes a URL to be loaded in a specified browser area when the command is executed.

We should be able to set the link URL, the link target, and whether the target page contains Javascript.

(See section 3.4.2.9 for information on how to set the parameters)

To use the parameters set in the configuration of UI components, you have to call the getParameters() method of the component. The method returns a hash table containing the parameter/value pairs.

As a parameter can contain more than one value, the value Object is of type ArrayList containing the multiple String values.

For example:

Parameter=value1;value2 ->; {Parameter/[value1, value2]}

Key: "Parameter"

Value: ArrayList ->; .get(0) : "value1"

->; .get(1) : "value2"

The following code example interprets the parameters set in the previous section (url=http://www.sap.com;_blank,isJavaScript=true) and returns a LinkAttributes object based on the parameters. The method getLinkAttributes is called by the flexible UI factory when the command is clicked.

The hash table corresponding to the parameters has the following structure:

{ "url" / ["http://www.sap.com", "_blank"] }, { "isJavaScript" / ["true"] }

public LinkAttributes getLinkAttributes() {
String url = "";
String target = "";
boolean isJavaScript = false;
Hashtable params = this.getParameters();
if(params != null){
if(params.get("url") != null){
ArrayList link = (ArrayList)params.get("url"); if(link.get(0) != null) url = (String)link.get(0);
if(link.get(1) != null) target = (String)link.get(1);
}
if(params.get("isJavaScript") != null){
ArrayList list = (ArrayList)params.get("isJavaScript");
if(list.get(0) != null) isJavaScript =
((String)list.get(0)).equals("true");
}
}
return new LinkAttributes(url, target, isJavaScript);


Configuration

Introduction

This section teaches you how to configure the single components.

Set up your flexible UI components in the KM Configuration iView (System Administration -> System Configuration -> Knowledge Management -> Configuration).

When you fill in the fields for configuration, note that the fields marked with an asterisk (*) are compulsory.

Property renderers are situated at Configuration -> Content Management -> Global Services -> Property Metadata.

All other flexible UI components are located at Configuration -> Content Management -> User Interface. The graphic to the right displays an overview of the navigation in the KM Configuration iView.

Note that these configuration steps can be performed only by a system administrator who has access to the portal configuration.

Property Renderer (Mapping)

Registering the Property Renderer

If you have implemented and deployed a custom property renderer, you can now add a mapping in the KM Configuration iView. In order to do this, follow the path stated above, choose Property Renderer, and then choose New.

A form like the form below appears. You need to specify at least the Name, Display Modes and Renderer Class. Then choose OK.

Your property renderer then appears in the list of property renderers.


Creating a New Property

After registering your property renderer, you need to create a new property for it in order to user the renderer.

To do this, choose Configuration -> Content Management -> Global Services -> Property Metadata -> Properties. Here you find a list of existing properties. Choose New to create your property.

The graphic below shows how the new property was configured for the subscription renderer in example 1, see section 1.4.4.2.1. Make sure that you specify at least the correct name of your property renderer. In the Icon Label field, specify the name of the icon you want to display for this property.

User Settings

There is on more step to carry out after you have finished configuring the new property: Add the new property to the displayed properties in your own component. To do this, choose Configuration -> Content Management -> User Interface -> Settings -> Collection Renderer Settings -> Collection List Renderer Settings and filter for your component.

When your component is displayed on the screen, select it and choose Edit. A similar screen to the one below appears. Add your property renderer to the Displayed Properties field and then choose OK.

This is the configuration for the subscription property described in example 1 of section 0.


Note that you have to restart the portal before you are able to see the new property in the Navigation iView.

Creating a Group Command

In the enhancement of example 2 of section 1.4.4.2, you need to create a command group in the configuration area in order to attach a hover menu to a property. This command group needs to contain the commands required for the hover menu. The procedure for this is explained below.

Resource Renderer (Mapping)

Resource Renderer Mapping

If you have implemented and deployed a custom resource renderer, you can now add a mapping. Specify at least an alias and your Java class.

Resource Renderer Settings

If you have created a mapping for your resource renderer, or if you want to use an existing resource renderer, you can create a new resource renderer setting here.

Specify at least a name and the resource renderer to be used.

If you want to use commands for each resource, you can add a command group for folders, links, and resources.

Collection Renderer

Collection Renderer Mapping

If you have implemented and deployed a custom collection renderer, you can now add a mapping. Specify at least an alias and your Java class.

Collection Renderer Settings

If you have created a mapping for your collection renderer yet, or if you want to use an existing collection renderer, you can create a new collection renderer setting here.

Specify at least a name and the collection renderer to be used.

You have to decide which type of collection renderer you want to create. Several types, such as Collection List Renderer Settings, Collection Tree Renderer Settings, Collection Grid Renderer Settings are available.

In the case of the example presented above the Collection Grid Renderer Settings were used:

Note that this configuration is specific for the collection renderer described above. For other collection renderers, the way you fill this form might differ.

In the configuration of this collection renderer one should pay close attention to the field Displayed Properties since the collection renderer uses this configuration to render the resources.

The collection renderer is very flexible. However the configuration is similar to the configuration of a grid collection renderer. In the following is an example of how you can configure this collection renderer (the same configuration as in the grid collection renderer for similar results).

Example:

In order to render your resources in this way you will have to specify the properties modifiers below:

rnd:icon([1-8;1]/alignTop/alignCenter/spaceRight=5)

rnd:displayname([1;2-2]/contentLink/spaceLeft=5/alignTop),

description([2;1-2]/spaceLeft=5)

modified(useTitleAsLabel/alignLeft/space-left=5)

rnd:permission([3;2]/alignRight/spaceLeft=10)

modifiedby(useTitleAsLabel/alignLeft/spaceLeft=5)

rnd:subscription([4;2]/alignRight/space-left=10)

owner(useTitleAsLabel/alignLeft/spaceLeft=5)

col:rating(alignLeft/[5;2]/spaceLeft=5)

dev:Release(useTitleAsLabel/alignLeft/spaceLeft=5)

dev:Department(useTitleAsLabel/alignLeft/spaceLeft=5)

rnd:action(alignRight/[8;1-2])

Application

If you want to use the collection renderer described above for example to display search results you have to make the following configuration changes. Go to Configuration -> Content Management -> User Interface -> Settings -> Collection Renderer Settings -> Search Result Renderer Settings and change the existing collection renderer to your new collection renderer. Now you have to specify the number of column and rows. Following, you also have to make a small change in the Displayed Properties filed: you have to specify the position for the title and the icon. The rest of the configuration remains the same:

Layout Controller

Mapping

If you have implemented and deployed a custom layout controller, you can now add a mapping. Specify at least an alias and your Java class.

Layout Set

You can now create a layout set to bring all your custom implementations together.

Specify the name of the layout set and the control renderers, resource renderers, and layout controller used.

Commands can be used in several locations.

Details Menu

You can set commands for the Details menu. Select a UICommandGroup for the structure of the Details menu.

Only the command groups contained and their children are displayed in a separate window when you select Details. Commands contained in the selected command group do not appear.

Resource Menu

To adjust the commands that appear for each resource, change the settings of the resource renderer.

Menu (in a Custom Position)

If the flavor Menu in the used in the selected layout controller, you have to add a collection renderer with the flavor Menu to your layout set.

This collection renderer is normally a basic collection renderer (setting).

Command

To create a new UI command, specify at least an alias and the Java class you want to use.

If you use a resource bundle, you have to specify the bundle file. Enter the name of the property file

(for example, my_UICConsts for my_UICConsts.properties or my_UICConsts_de.properties)

Commands and command groups can be associated with your flexible UI component in the layout set (Details menu and components with the flavor Menu) and in the resource renderer settings (the menu to the right of each resource).

Command Group

To create a new UI command group, specify at least an alias and the Java class you want to use. If you have not implemented a custom UI command group, keep the value com.sapportals.wcm.rendering.uicommand.UIGroupCommand.

If you use a resource bundle, you have to specify the bundle file. Enter the name of the property file

(for example, my_UICConsts for my_UICConsts.properties or my_UICConsts_de.properties)

You can specify a key for the label and a key for the tooltip, and add the key=value pairs to your resource bundle. The values associated with the keys are displayed.

Add the UI commands and/or UI command groups to be displayed to the Command List field (comma-separated).

Parameters

As an example, we implemented a URL redirect UI command in section 1.5. This causes a URL to be loaded in a specified browser area when the command is executed.

We should be able to set the link URL, the link target, and whether the target page contains Javascript.

Enter your parameter/value pairs in the Parameters field.

The field has to be filled correctly - otherwise the getParameters() method returns null.

Format the string as follows:

parameter=value

To use more parameters, separate them with commas:

parameter1=value1,parameter2=value2

If a parameter contains more than one value, separate the values with semicolons:

parameter=value;value2;value3

For our example, we set the Parameters value to:

url=http://www.sap.com;_blank,isJavaScript=true

->; The page to be loaded is www.sap.com

->; The page is opened in a separate window

->; The page uses Javascript